#include <zenilib.h>
#include "PlayState.h"
#include "Globals.h"
#include "Boss.h"
#include "Civilian.h"
#include "GameOver.h"
#include <fstream>

using namespace Zeni;
using namespace std;

const SDLKey pause_key = SDLK_p;

PlayState::PlayState(int levelNum_, int points_) :
	levelNum(levelNum_),
	m_paused(false),
	playTime(0.0f),
	points(points_)
{
	ifstream iFile;
	iFile.open(("levels/Level" + itos(levelNum) + ".txt").c_str());
	iFile >> levelName;

	float x, y, width, height;
	string texture, heading;
	iFile >> heading >> x >> y;
	player = new Player(Vector2(x, y));

	float minX = player->getBody()->left();
	float maxX = player->getBody()->right();
	float minY = player->getBody()->top();
	float maxY = player->getBody()->bottom();

	while (!iFile.eof()){
		iFile >> heading;
		if (heading == "Platform"){
			iFile >> x >> y >> width >> height >> texture;
			platforms.push_back(new Platform(Vector2(x, y), Vector2(width, height), texture));
		}
		else if (heading == "Civilian"){
			iFile >> x >> y;
			npcs.push_back(new Civilian(Vector2(x, y)));
		}
		else if (heading == "Enemy"){
			iFile >> x >> y;
			npcs.push_back(new Enemy(Vector2(x, y)));
		}
		else if (heading == "Boss"){
			iFile >> x >> y;
			npcs.push_back(new Boss(Vector2(x, y)));
		}
		else if (heading == "HealthPack") {
			iFile >> x >> y;
			healthPacks.push_back(new HealthPack(Vector2(x, y)));
		}
		else{
			break;
		}
	}
	iFile.close();

	//Create a vector of 25 sound sources to be used.
	soundsStopped.reserve(25);
	for(int i = 0; i < 25; i++)
	{
		Sound_Source* ss = new Sound_Source();
		soundsStopped.push_back(ss);
	}

	for (int i = 0; i < platforms.size(); i++) {
		if (platforms[i]->left() < minX){
			minX = platforms[i]->left();
		}
		if (platforms[i]->right() > maxX){
			maxX = platforms[i]->right();
		}
		if (platforms[i]->top() < minY){
			minY = platforms[i]->top();
		}
		if (platforms[i]->bottom() > maxY){
			maxY = platforms[i]->bottom();
		}
	} 
	for (int i = 0; i < npcs.size(); i++) {
		if (npcs[i]->getBody()->left() < minX){
			minX = npcs[i]->getBody()->left();
		}
		if (npcs[i]->getBody()->right() > maxX){
			maxX = npcs[i]->getBody()->right();
		}
		if (npcs[i]->getBody()->top() < minY){
			minY = npcs[i]->getBody()->top();
		}
		if (npcs[i]->getBody()->bottom() > maxY){
			maxY = npcs[i]->getBody()->bottom();
		}
	}
	minX -= 50;
	maxX += 50;
	minY -= screenHeight;
	float infoBarHeight = screenHeight / 12;
	backgroundHeight = screenHeight - infoBarHeight;
	platforms.push_back(new Platform(Vector2(minX - 50, 0), Vector2(100, 2 * INFINITY), ""));
	platforms.push_back(new Platform(Vector2(maxX + 50, 0), Vector2(100, 2 * INFINITY), ""));
	minX -= screenWidth;
	maxX += screenWidth;
	platforms.push_back(new Platform(Vector2((minX + maxX) / 2, maxY + infoBarHeight), Vector2(maxX - minX, infoBarHeight * 2), "Ground"));
	background = get_Video().create_Vertex_Buffer();
	float sx = (maxX - minX) / (2 * screenWidth);
	float sy = 1.0f;
	Vertex2f_Texture ul(Point2f(minX, maxY - backgroundHeight * 2), Point2f(0.0f, 0.01f));
	Vertex2f_Texture ll(Point2f(minX, maxY), Point2f(0.0f, sy));
	Vertex2f_Texture lr(Point2f(maxX, maxY), Point2f(sx, sy));
	Vertex2f_Texture ur(Point2f(maxX, maxY - backgroundHeight * 2), Point2f(sx, 0.01f));
	Quadrilateral<Vertex2f_Texture> *quad = new Quadrilateral<Vertex2f_Texture>(ul, ll, lr, ur);
	quad->give_Material(new Material("Background1"));
	
	sy = (maxY - minY - backgroundHeight * 2) / backgroundHeight;
	Vertex2f_Texture ul2(Point2f(minX, minY), Point2f(0.0f, 0.01f));
	Vertex2f_Texture ll2(Point2f(minX, maxY - backgroundHeight * 2), Point2f(0.0f, sy));
	Vertex2f_Texture lr2(Point2f(maxX, maxY - backgroundHeight * 2), Point2f(sx, sy));
	Vertex2f_Texture ur2(Point2f(maxX, minY), Point2f(sx, 0.01f));
	Quadrilateral<Vertex2f_Texture> *quad2 = new Quadrilateral<Vertex2f_Texture>(ul2, ll2, lr2, ur2);
	quad2->give_Material(new Material("Background2"));
	background->give_quadrilateral(quad2);
	background->give_quadrilateral(quad);
	screenTop = baseScreenTop = maxY + infoBarHeight - screenHeight;

	//render platforms to buffer
	sx = 1.0f;
	sy = 1.0f;
	for (int i = 0; i < platforms.size(); i++){
		if (platforms[i]->getTexture(0) != ""){
			Vertex2f_Texture ul3(platforms[i]->getPoint(0).toPoint2f(), Point2f(0.01f, 0.01f));
			Vertex2f_Texture ll3(platforms[i]->getPoint(1).toPoint2f(), Point2f(sx, 0.01f));
			Vertex2f_Texture lr3(platforms[i]->getPoint(2).toPoint2f(), Point2f(sx, sy));
			Vertex2f_Texture ur3(platforms[i]->getPoint(3).toPoint2f(), Point2f(0.01f, sy));
			Quadrilateral<Vertex2f_Texture> *platform = new Quadrilateral<Vertex2f_Texture>(ul3, ll3, lr3, ur3);
			platform->give_Material(new Material(platforms[i]->getTexture(0)));
			background->give_quadrilateral(platform);
		}
	}
}

PlayState::~PlayState()
{
	for (int i = 0; i < platforms.size(); i++){
		delete platforms[i];
	}
	platforms.clear();
	for (int i = 0; i < npcs.size(); i++) {
		delete npcs[i];
	}
	npcs.clear();
	for (int i = 0; i < projectiles.size(); i++) {
		delete projectiles[i];
	}
	projectiles.clear();
	for (int i = 0; i < effects.size(); i++) {
		delete effects[i];
	}
	for (int i = 0; i < soundsPlaying.size(); i++)
		delete soundsPlaying[i];
	for (int i = 0; i < soundsStopped.size(); i++)
		delete soundsStopped[i];
	effects.clear();
	delete player;
	delete background;
}

void PlayState::on_key(const SDL_KeyboardEvent &event)
{
	Game &gr = get_Game();
	switch(event.keysym.sym){
		case SDLK_ESCAPE:
			gr.pop_state();
			break;
		case SDLK_w:
			player->controls.up = (event.type == SDL_KEYDOWN);
			break;
		case SDLK_a:
			player->controls.left = (event.type == SDL_KEYDOWN);
			break;
		case SDLK_s:
			player->controls.down = (event.type == SDL_KEYDOWN);
			break;
		case SDLK_d:
			player->controls.right = (event.type == SDL_KEYDOWN);
			break;
		case SDLK_SPACE:
			player->controls.jump = (event.type == SDL_KEYDOWN);
			break;
		case SDLK_j:
			player->controls.fire = (event.type == SDL_KEYDOWN);
			break;
		case SDLK_k:
			player->controls.personJump = (event.type == SDL_KEYDOWN);
			break;
		case pause_key:
			if (event.type == SDL_KEYDOWN){
				m_paused = !m_paused;
			}
			break;
		default:
			break;
	}
}

void PlayState::weaponFire(Player* p)
{
	if (p->controls.fire){
		p->controls.fire = false;
		if (p->playertype != BOSS){
			vector<Projectile*> firedProjectiles = p->fireWeapon();
			for (int i = 0; i < firedProjectiles.size(); i++)
			{
				if(firedProjectiles[i]->getWeaponType() == UZI)
					playSound("UziFire");
				else if(firedProjectiles[i]->getWeaponType() == LASER)
					playSound("Laser");
				else if(firedProjectiles[i]->getWeaponType() == SHURIKEN)
					playSound("Throw");
				else if(firedProjectiles[i]->getWeaponType() == BIGGUN)
					playSound("Missile");
				else
					playSound("UziFire");
					
				projectiles.push_back(firedProjectiles[i]);
			}
		}
		else{
			if (p->getBody()->getTexture(1) == "Boss1Jump"){
				p->getBody()->setRot(0);
				p->getBody()->setRotVel(0);
			}
			p->getBody()->setTexture(1, "Boss1Attack");
			p->walkVelocity *= 1.5f;
			p->getBody()->setScale(Vector2(150, 150));
		}
	}
}

void PlayState::playSound(string soundname)
{
	if(!soundsStopped.empty())
	{
		Sound_Source* sound = soundsStopped.back();
		sound->set_buffer(get_Sounds()[soundname]);
		sound->play();
		soundsPlaying.push_back(sound);
		soundsStopped.pop_back();
	}
}

void PlayState::brainSlugJump(Player* p)
{
	if (p->controls.personJump){
		p->controls.personJump = false;
		if (p->controlsPerson){
			Civilian* c = new Civilian(p->jumpOff());
			c->setWeaponType(p->type);
			npcs.push_back(c);
		}
		else if (!p->jumpTarget){
			//determine who to jump on if anyone
			int closest = -1;
			float minDistance = INFINITY;
			for (int i = 0; i < npcs.size(); i++){
				if (npcs[i]->playertype == CIVILIAN && !npcs[i]->dead()){
					float d = p->distanceFrom(npcs[i]);
					if (d < minDistance && d < 1.5f * (p->getBody()->getRadius() + npcs[i]->getBody()->getRadius())){
						closest = i;
						minDistance = d;
					}
				}
			}
			if (closest != -1){
				p->jumpTarget = npcs[closest];
			}
		}
	}
}

void PlayState::perform_logic()
{
	static Time previous;
	Time current = get_Timer().get_time();
	float timestep = current.get_seconds_since(previous);
	previous = current;
	if (timestep > 0.1f){
		timestep = 0.1f;
	}

	// Check for non playing Sounds and push them back to correct vector
	for(int i = 0; i < this->soundsPlaying.size(); i++)
	{
		if(soundsPlaying[i]->is_stopped())
		{
			soundsStopped.push_back(soundsPlaying[i]);
			soundsPlaying.erase(soundsPlaying.begin() + i);
			i--;
		}
	}

	if (!m_paused){
		playTime += timestep;

		if(player->timeSinceLastDamage < 3.0f)
			player->timeSinceLastDamage += timestep;
		for(int i = 0; i < npcs.size(); i++)
		{
			if(npcs[i]->timeSinceLastDamage < 3.0f)
				npcs[i]->timeSinceLastDamage += timestep;
		}

		// Check if player's health <=0 and quit
		if(player->controlsPerson)
		{
			if(player->getBody()->hostHealth + player->getBrain()->hostHealth <= 0){
				get_Game().pop_state();
				get_Game().push_state(new GameOver("Lose"));
				return;
			}
			if(player->dead())
			{
				Civilian* c = new Civilian(player->jumpOff());
				c->setWeaponType(player->type);
				npcs.push_back(c);
			}
		}
		else
		{
			if(player->dead()){
				get_Game().pop_state();
				get_Game().push_state(new GameOver("Lose"));
				return;
			}
		}

		for (int i = 0; i < platforms.size(); i++){
			platforms[i]->update(timestep);
		}
		player->update(timestep);
		for (int i = 0; i < npcs.size(); i++) {
			if (!npcs[i]->dead()){
				npcs[i]->determineAction(platforms,npcs,player,timestep);
				weaponFire(npcs[i]);
			}
			//else{
			//	if (npcs[i]->controlsPerson && (npcs[i]->playertype == ENEMY || npcs[i]->playertype == BOSS)){
			//		Civilian* c = new Civilian(npcs[i]->jumpOff());
			//		c->setWeaponType(player->type);
			//		npcs[i]->getBody()->hostHealth = -1;
			//		npcs.push_back(c);
			//	}
			//}
			npcs[i]->update(timestep);
		}
		for (int i = 0; i < projectiles.size(); i++) {
			projectiles[i]->update(timestep);
		}
		for (int i = 0; i < effects.size(); i++) {
			effects[i]->update(timestep);
			if (effects[i]->finished()){
				delete effects[i];
				effects.erase(effects.begin() + i);
				i--;
			}
		}
		for (int i = 0; i < platforms.size(); i++){
			for (int j = 0; j < npcs.size(); j++){
				if (npcs[j]->playerCanSee){
					npcs[j]->handlePlatformCollision(platforms[i], timestep);
				}
			}
			player->handlePlatformCollision(platforms[i], timestep);
		}
		for (int i = 0; i < npcs.size(); i++){
			if (npcs[i]->playerCanSee && !npcs[i]->dead()){
				player->handlePlayerCollision(npcs[i], timestep);
			}
		}
		// handle health packs
		for (int i = 0; i < healthPacks.size(); i++)
		{
			if(player->getBody()->distanceFrom(healthPacks[i]) < 1000) {
				if(player->handleHealthPackCollsion(healthPacks[i])) {
					delete healthPacks[i];
					healthPacks.erase(healthPacks.begin() + i);
				}
			}
		}

		for (int i = 0; i < npcs.size(); i++){
			npcs[i]->setExtrasInPosition();
		}
		player->setExtrasInPosition();

		weaponFire(player);
		for (int i = 0; i < npcs.size(); i++){
			brainSlugJump(npcs[i]);
		}
		brainSlugJump(player);

		//determine projectile hits
		for (int i = 0; i < projectiles.size(); i++) {
			bool projectileHit = false;
			for (int j = 0; j < npcs.size(); j++){
				if (!npcs[j]->dead()){
					if (npcs[j]->handleProjectileCollision(projectiles[i])){
						if (projectiles[i]->getWeaponType() == BIGGUN){
							effects.push_back(new ExplosionEffect(projectiles[i]->getPos() + projectiles[i]->getVel() * timestep));
							playSound("Explosion");
						}
						else if (projectiles[i]->getWeaponType() != LASER && npcs[j]->controlsPerson){
							effects.push_back(new BloodEffect(projectiles[i]->getPos() + projectiles[i]->getVel() * timestep));
							playSound("Blood");
							if (projectiles[i]->getVel().x > 0){
								effects.back()->setFlipped(true);
							}
							effects.back()->setVel(npcs[j]->getBody()->getVel());
						}
						switch (npcs[j]->playertype){
							case CIVILIAN:
								if (projectiles[i]->getFiredBy() == player){
									if (npcs[j]->dead()){
										points -= 200;
									}
									else{
										points -= projectiles[i]->damage / 2;
									}
								}
								break;
							case ENEMY:
								if (npcs[j]->dead()){
									points += 400;
								}
								else if (projectiles[i]->getFiredBy() == player){
									points += projectiles[i]->damage;
								}
								break;
							case BOSS:
								if (npcs[j]->dead()){
									points += 2000;
								}
								else if (projectiles[i]->getFiredBy() == player){
									points += projectiles[i]->damage * 2;
								}
								break;
						}
						projectileHit = projectiles[i]->getWeaponType() != LASER;
					}
				}
			}
			if (!projectileHit){
				if (player->handleProjectileCollision(projectiles[i])){
					projectileHit = true;
					if (projectiles[i]->getWeaponType() == BIGGUN){
						effects.push_back(new ExplosionEffect(projectiles[i]->getPos() + projectiles[i]->getVel() * timestep));
						playSound("Explosion");
					}
					else if (player->controlsPerson){
						effects.push_back(new BloodEffect(projectiles[i]->getPos() + projectiles[i]->getVel() * timestep));
						playSound("Blood");
						if (projectiles[i]->getVel().x > 0){
							effects.back()->setFlipped(true);
						}
						effects.back()->setVel(player->getBody()->getVel());
					}
				}
			}
			if (!projectileHit){
				for (int j = 0; j < platforms.size(); j++){
					if (Collision::objectsIntersect(platforms[j], projectiles[i])){
						if (projectiles[i]->getWeaponType() == BIGGUN){
							effects.push_back(new ExplosionEffect(projectiles[i]->getPos()));
							playSound("Explosion");
						}
						projectileHit = true;
						break;
					}
				}
			}
			if (projectileHit){
				delete projectiles[i];
				projectiles.erase(projectiles.begin() + i);
			}
		}

		//remove all gone npcs
		bool bossExists = false;
		for (int i = 0; i < npcs.size(); i++){
			if (npcs[i]->gone){
				delete npcs[i];
				npcs.erase(npcs.begin() + i);
				i--;
				continue;
			}
			if (npcs[i]->playertype == BOSS){
				bossExists = true;
			}
		}
		if (!bossExists){
			get_Game().pop_state();
			if (++levelNum <= 3){
				get_Game().push_state(new PlayState(levelNum, points));
				get_Game().push_state(new GameOver("LevelComplete"));
			}
			else{
				get_Game().push_state(new GameOver("YouWin"));
			}
			return;
		}

		Vector2 vel = player->getBody()->getVel();
		if (player->getBody()->top() < screenTop + screenHeight / 4){
			if (vel.y < 0){
				screenTop += vel.y * timestep;
			}
		}
		else if (player->getBody()->bottom() > screenTop + screenHeight * 3 / 4){
			if (vel.y > 0){
				screenTop += player->getBody()->getVel().y * timestep;
				if (screenTop > baseScreenTop){
					screenTop = baseScreenTop;
				}
			}
		}
		Vector2 playerPos = player->getPos();
		viewArea.first = Point2f(playerPos.x - screenWidth / 2, screenTop);
		viewArea.second = Point2f(playerPos.x + screenWidth / 2, screenTop + screenHeight);
	}
}

void PlayState::render()
{
	Video &vr = Zeni::get_Video();
	vr.set_2d(viewArea);
	background->render();
	for (int i = 0; i < projectiles.size(); i++) {
		if (player->getBody()->distanceFrom(projectiles[i]) < 1000){
			projectiles[i]->render();
		}
	}
	for (int i = 0; i < npcs.size(); i++){
		if (npcs[i]->playerCanSee){
			npcs[i]->render();
		}
	}
	for (int i = 0; i < healthPacks.size(); i++) {
		if (player->getBody()->distanceFrom(healthPacks[i]) < 1000)
			healthPacks[i]->render();
	}
	if((player->timeSinceLastDamage > .25 && player->timeSinceLastDamage < .5) ||
		 (player->timeSinceLastDamage > .75 && player->timeSinceLastDamage < 1.0) ||
		 (player->timeSinceLastDamage > 1.25 && player->timeSinceLastDamage < 1.5) ||
		 (player->timeSinceLastDamage > 1.75 && player->timeSinceLastDamage < 2.0) ||
		 (player->timeSinceLastDamage > 2.25))
	player->render();
	for (int i = 0; i < effects.size(); i++) {
		effects[i]->render();
	}

	vr.set_2d();
	const Font &font = get_Fonts()["gameInfo"];
	//font.render_text(itos(get_Game().get_fps()), Point2f(10, 10), Color());
	float x = screenWidth - font.get_text_width("Points: 99999");
	float y = 5;
	font.render_text("Points: " + itos(points), Point2f(x, y), Color());
	y += font.get_text_height() + 5;
	font.render_text("Time: " + timeDisplay(playTime), Point2f(x, y), Color());
}
